package com.javaop.BNetLogin.cdkey;

/*
 * Alpha26Decode.java
 * 
 * Created on May 21, 2004, 3:23 AM
 */

import com.javaop.util.Buffer;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import com.javaop.BNetLogin.password.ByteFromIntArray;
import com.javaop.BNetLogin.password.IntFromByteArray;

/**
 * 
 * @author iago, wjlafrance
 */
class Alpha26Decode extends Decode {
    
    public final static byte[] KeyTable = {
        (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0x00, (byte)0xFF, (byte)0x01, (byte)0xFF, (byte)0x02, (byte)0x03,
        (byte)0x04, (byte)0x05, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0x06, (byte)0x07, (byte)0x08, (byte)0x09, (byte)0x0A, (byte)0x0B,
        (byte)0x0C, (byte)0xFF, (byte)0x0D, (byte)0x0E, (byte)0xFF, (byte)0x0F, (byte)0x10, (byte)0xFF,
        (byte)0x11, (byte)0xFF, (byte)0x12, (byte)0xFF, (byte)0x13, (byte)0xFF, (byte)0x14, (byte)0x15,
        (byte)0x16, (byte)0x17, (byte)0x18, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0x06, (byte)0x07, (byte)0x08, (byte)0x09, (byte)0x0A, (byte)0x0B,
        (byte)0x0C, (byte)0xFF, (byte)0x0D, (byte)0x0E, (byte)0xFF, (byte)0x0F, (byte)0x10, (byte)0xFF,
        (byte)0x11, (byte)0xFF, (byte)0x12, (byte)0xFF, (byte)0x13, (byte)0xFF, (byte)0x14, (byte)0x15,
        (byte)0x16, (byte)0x17, (byte)0x18, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
        (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF
    };

    public final static byte[] TranslateTable = {
        0x09, 0x04, 0x07, 0x0F, 0x0D, 0x0A, 0x03, 0x0B, 0x01, 0x02, 0x0C, 0x08, 0x06, 0x0E, 0x05, 0x00,
        0x09, 0x0B, 0x05, 0x04, 0x08, 0x0F, 0x01, 0x0E, 0x07, 0x00, 0x03, 0x02, 0x0A, 0x06, 0x0D, 0x0C,
        0x0C, 0x0E, 0x01, 0x04, 0x09, 0x0F, 0x0A, 0x0B, 0x0D, 0x06, 0x00, 0x08, 0x07, 0x02, 0x05, 0x03,
        0x0B, 0x02, 0x05, 0x0E, 0x0D, 0x03, 0x09, 0x00, 0x01, 0x0F, 0x07, 0x0C, 0x0A, 0x06, 0x04, 0x08,
        0x06, 0x02, 0x04, 0x05, 0x0B, 0x08, 0x0C, 0x0E, 0x0D, 0x0F, 0x07, 0x01, 0x0A, 0x00, 0x03, 0x09,
        0x05, 0x04, 0x0E, 0x0C, 0x07, 0x06, 0x0D, 0x0A, 0x0F, 0x02, 0x09, 0x01, 0x00, 0x0B, 0x08, 0x03,
        0x0C, 0x07, 0x08, 0x0F, 0x0B, 0x00, 0x05, 0x09, 0x0D, 0x0A, 0x06, 0x0E, 0x02, 0x04, 0x03, 0x01,
        0x03, 0x0A, 0x0E, 0x08, 0x01, 0x0B, 0x05, 0x04, 0x02, 0x0F, 0x0D, 0x0C, 0x06, 0x07, 0x09, 0x00,
        0x0C, 0x0D, 0x01, 0x0F, 0x08, 0x0E, 0x05, 0x0B, 0x03, 0x0A, 0x09, 0x00, 0x07, 0x02, 0x04, 0x06,
        0x0D, 0x0A, 0x07, 0x0E, 0x01, 0x06, 0x0B, 0x08, 0x0F, 0x0C, 0x05, 0x02, 0x03, 0x00, 0x04, 0x09,
        0x03, 0x0E, 0x07, 0x05, 0x0B, 0x0F, 0x08, 0x0C, 0x01, 0x0A, 0x04, 0x0D, 0x00, 0x06, 0x09, 0x02,
        0x0B, 0x06, 0x09, 0x04, 0x01, 0x08, 0x0A, 0x0D, 0x07, 0x0E, 0x00, 0x0C, 0x0F, 0x02, 0x03, 0x05,
        0x0C, 0x07, 0x08, 0x0D, 0x03, 0x0B, 0x00, 0x0E, 0x06, 0x0F, 0x09, 0x04, 0x0A, 0x01, 0x05, 0x02,
        0x0C, 0x06, 0x0D, 0x09, 0x0B, 0x00, 0x01, 0x02, 0x0F, 0x07, 0x03, 0x04, 0x0A, 0x0E, 0x08, 0x05,
        0x03, 0x06, 0x01, 0x05, 0x0B, 0x0C, 0x08, 0x00, 0x0F, 0x0E, 0x09, 0x04, 0x07, 0x0A, 0x0D, 0x02,
        0x0A, 0x07, 0x0B, 0x0F, 0x02, 0x08, 0x00, 0x0D, 0x0E, 0x0C, 0x01, 0x06, 0x09, 0x03, 0x05, 0x04,
        0x0A, 0x0B, 0x0D, 0x04, 0x03, 0x08, 0x05, 0x09, 0x01, 0x00, 0x0F, 0x0C, 0x07, 0x0E, 0x02, 0x06,
        0x0B, 0x04, 0x0D, 0x0F, 0x01, 0x06, 0x03, 0x0E, 0x07, 0x0A, 0x0C, 0x08, 0x09, 0x02, 0x05, 0x00,
        0x09, 0x06, 0x07, 0x00, 0x01, 0x0A, 0x0D, 0x02, 0x03, 0x0E, 0x0F, 0x0C, 0x05, 0x0B, 0x04, 0x08,
        0x0D, 0x0E, 0x05, 0x06, 0x01, 0x09, 0x08, 0x0C, 0x02, 0x0F, 0x03, 0x07, 0x0B, 0x04, 0x00, 0x0A,
        0x09, 0x0F, 0x04, 0x00, 0x01, 0x06, 0x0A, 0x0E, 0x02, 0x03, 0x07, 0x0D, 0x05, 0x0B, 0x08, 0x0C,
        0x03, 0x0E, 0x01, 0x0A, 0x02, 0x0C, 0x08, 0x04, 0x0B, 0x07, 0x0D, 0x00, 0x0F, 0x06, 0x09, 0x05,
        0x07, 0x02, 0x0C, 0x06, 0x0A, 0x08, 0x0B, 0x00, 0x0F, 0x04, 0x03, 0x0E, 0x09, 0x01, 0x0D, 0x05,
        0x0C, 0x04, 0x05, 0x09, 0x0A, 0x02, 0x08, 0x0D, 0x03, 0x0F, 0x01, 0x0E, 0x06, 0x07, 0x0B, 0x00,
        0x0A, 0x08, 0x0E, 0x0D, 0x09, 0x0F, 0x03, 0x00, 0x04, 0x06, 0x01, 0x0C, 0x07, 0x0B, 0x02, 0x05,
        0x03, 0x0C, 0x04, 0x0A, 0x02, 0x0F, 0x0D, 0x0E, 0x07, 0x00, 0x05, 0x08, 0x01, 0x06, 0x0B, 0x09,
        0x0A, 0x0C, 0x01, 0x00, 0x09, 0x0E, 0x0D, 0x0B, 0x03, 0x07, 0x0F, 0x08, 0x05, 0x02, 0x04, 0x06,
        0x0E, 0x0A, 0x01, 0x08, 0x07, 0x06, 0x05, 0x0C, 0x02, 0x0F, 0x00, 0x0D, 0x03, 0x0B, 0x04, 0x09,
        0x03, 0x08, 0x0E, 0x00, 0x07, 0x09, 0x0F, 0x0C, 0x01, 0x06, 0x0D, 0x02, 0x05, 0x0A, 0x0B, 0x04,
        0x03, 0x0A, 0x0C, 0x04, 0x0D, 0x0B, 0x09, 0x0E, 0x0F, 0x06, 0x01, 0x07, 0x02, 0x00, 0x05, 0x08
    };
            
    private int KEYLEN = 26;
    private int BUFLEN = 52;
    
    private int val1;
    private byte[] val2;
    private int product;
    
    public Alpha26Decode(String cdkey) throws IllegalArgumentException {
        
        if(cdkey == null || cdkey.isEmpty())
            throw new IllegalArgumentException("CD-Key is missing!");
        
        if(cdkey.length() != KEYLEN)
            throw new IllegalArgumentException("CDKey is not 26 characters!");
        
        byte[] table = new byte[BUFLEN];
        int[] values = new int[4];
        tableLookup(cdkey.toUpperCase(), table);
        
        for (int i = BUFLEN; i > 0; i--)
            Mult(4, 5, values, values, table[i - 1]);
        
        decodeKeyTablePass1(values);
        decodeKeyTablePass2(values);
        
        product = values[0] >> 0x0a;
        val1 = ((values[0] & 0x03FF) << 0x10) | (values[1] >>> 0x10);

        val2 = new byte[10];
        val2[0] = (byte) ((values[1] & 0x00FF) >> 0);
        val2[1] = (byte) ((values[1] & 0xFF00) >> 8);
        
        IntFromByteArray.LITTLEENDIAN.insertInteger(val2, 2, values[2]);
        IntFromByteArray.LITTLEENDIAN.insertInteger(val2, 6, values[3]);
    }
    
    private void tableLookup(String key, byte[] buf) {
        int a;
        int b = 0x21;
        byte decode;

        for (int i = 0; i < KEYLEN; i++) {
            a = (b + 0x07B5) % BUFLEN;
            b = (a + 0x07B5) % BUFLEN;
            decode = KeyTable[key.charAt(i)];
            buf[a] = (byte) (decode / 5);
            buf[b] = (byte) (decode % 5);
        }
    }
    
    private void Mult(int rounds, int mulx, int[] bufA, int[] bufB, int decodedByte) {
        int posA = rounds - 1;
        int posB = rounds - 1;
        
        while (rounds-- > 0) {
            long param1 = bufA[posA--];
            param1 &= 0x00000000FFFFFFFFl;
            
            long param2 = mulx;
            param2 &= 0x00000000FFFFFFFFl;
            
            long edxeax = param1 * param2;
            
            // ULONGLONG edxeax = UInt32x32To64(*BufA--, Mulx);
            bufB[posB--] = decodedByte + (int) edxeax;
            decodedByte = (int) (edxeax >> 32);
        }
    }

    private void decodeKeyTablePass1(int[] keyTable) {
        int ebx, ecx, esi, ebp;
        int var_C, var_4;
        int var_8 = 29;

        for (int i = 464; i >= 0; i -= 16) {
            esi = (var_8 & 7) << 2;
            var_4 = var_8 >>> 3;
            var_C = (keyTable[3 - var_4] & (0x0F << esi)) >>> esi;

            if (i < 464) {
                for (int j = 29; j > var_8; j--) {
                    ecx = (j & 7) << 2;
                    ebp = (keyTable[0x03 - (j >>> 3)] & (0x0F << ecx)) >>> ecx;
                    var_C = TranslateTable[ebp ^ TranslateTable[var_C + i] + i];
                }
            }

            for (int j = --var_8; j >= 0; j--) {
                ecx = (j & 7) << 2;
                ebp = (keyTable[0x03 - (j >>> 3)] & (0x0F << ecx)) >>> ecx;
                var_C = TranslateTable[ebp ^ TranslateTable[var_C + i] + i];
            }

            int index = 3 - var_4;
            ebx = (TranslateTable[var_C + i] & 0x0F) << esi;
            keyTable[index] = (ebx | ~(0x0F << esi) & (keyTable[index]));
        }
    }

    void decodeKeyTablePass2(int[] keyTable) {
        int eax, edx, ecx, edi, esi, ebp;
        byte[] Copy = ByteFromIntArray.LITTLEENDIAN.getByteArray(keyTable);
        esi = 0;

        for (edi = 0; edi < 120; edi++) {
            eax = edi & 0x1F;
            ecx = esi & 0x1F;
            edx = 3 - (edi >>> 5);

            // ebp = *(DWORD *)((BYTE *)(Copy+3) - ((esi >> 5) << 2)); <--
            // original c++ code
            // could you convert that line to java?
            int location = 12 - ((esi >>> 5) << 2);
            ebp = IntFromByteArray.LITTLEENDIAN.getInteger(Copy, location);

            // System.out.print(PadString.padHex(ebp, 8) + " ");

            ebp = (ebp & (1 << ecx)) >>> ecx;
            keyTable[edx] = ((ebp & 1) << eax) | (~(1 << eax) & keyTable[edx]);
            esi += 0x0B;
            if (esi >= 120)
                esi -= 120;
        }
    }
    
    public int[] getKeyHash(int clientToken, int serverToken) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            
            byte[] warBuf = new byte[26];
            
            IntFromByteArray.LITTLEENDIAN.insertInteger(warBuf, 0, clientToken);
            IntFromByteArray.LITTLEENDIAN.insertInteger(warBuf, 4, serverToken);
            IntFromByteArray.LITTLEENDIAN.insertInteger(warBuf, 8, getProduct());
            IntFromByteArray.LITTLEENDIAN.insertInteger(warBuf, 12, getVal1());
            
            for(int i = 16; i < 26; i++)
                warBuf[i] = getWar3Val2()[i - 16];
            
            digest.update(warBuf);
            return IntFromByteArray.LITTLEENDIAN.getIntArray(digest.digest());
        } catch(NoSuchAlgorithmException e) {
            System.out.println("Could not find SHA1 library " + e);
            System.exit(1);
            return null;
        }
    }

    
    public int getVal1() {
        return val1;
    }
    
    public int getVal2() {
        throw new UnsupportedOperationException("Can't use Alpha26Decode's getVal2() as an int");
    }
    
    public byte[] getWar3Val2() {
        return val2;
    }
    
    public int getProduct() {
        return product;
    }
    
    public String toString() {
        return "26-character alphanumeric decoder";
    }
}
